﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using iTextSharp.text;
using iTextSharp.text.pdf;
using SdmxMl.Common;
using SdmxMl.Manager;
using SdmxMl.Structure;

namespace ChopPlugin
{
    #region Margin Structure

    public struct ChopMargin
    {
        public float Top;
        public float Bottom;
        public float Left;
        public float Right;

        public void SwitchMargin()
        {
            float temp = Left;
            Left = Right;
            Right = temp;
        }
    }

    #endregion


    /// <summary>
    /// Generation of CHOP Pdf document from SDMX CHOP Hierarchical codelist
    /// </summary>
    public class PdfChop
    {
        #region Internal Members

        /// <summary> Loader, container for SDMX artefacts </summary>
        private SmManager sm;
        /// <summary>Dictionnary of codelists used for CHOP hierarchy representation </summary>
        private Dictionary<string, CodeListType> dico;

        /// <summary>All different fonts used</summary>
        private Font fontCode;
        private Font fontExclude;
        private Font fontRemark;
        private Font fontExcludeTag;
        private Font fontIncludeTag;
        private Font fontHeader;
        private Font fontHeaderLevel1;
        private Font fontFooter;

        /// <summary>ITextSharp objects for Pdf document generation </summary>
        private PdfWriter docWriter;
        private Document doc;

        /// <summary> List of tables generated for each code of CHOP </summary>
        private List<CodeTableRepresentation> codeTableList;

        /// <summary>The Chop as SDMX Hierarchical codelist representation </summary>
        private HierarchicalCodelistType hcl;

        /// <summary>Options parameters for Pdf generation </summary>
        private PdfOptions pdfOptions;

        private ChopMargin margins;

        /// <summary>Additional texts managed in different languages </summary>
        private MultiLang internationalExclude;
        private MultiLang internationalInclude;
        private MultiLang internationalNote;
        private MultiLang internationalCodeAlso;


        /// Fonts embedded
        BaseFont bfBold;
        BaseFont bfItalic;
        BaseFont bfRoman;
        BaseFont bfBlack;

        BaseFont bfArialBold;
        BaseFont bfArial;
        BaseFont bfArialItalic;


        #endregion

        #region Constructor

        /// <summary> Constructor, initializes the different fonts used in Pdf </summary>
        public PdfChop()
        {
            // Embedding font 
                        // Check Existence of required sub directories
            string appDir = System.IO.Path.GetDirectoryName(System.Windows.Forms.Application.ExecutablePath) 
                + Path.DirectorySeparatorChar + PluginManager.PLUG_IN_SUBDIR_NAME;


            bfBold = BaseFont.CreateFont(appDir + @"\SyntaxLTStd-Bold.otf", BaseFont.IDENTITY_H, BaseFont.EMBEDDED);
            bfItalic = BaseFont.CreateFont(appDir + @"\SyntaxLTStd-Italic.otf", BaseFont.IDENTITY_H, BaseFont.EMBEDDED);
            bfRoman = BaseFont.CreateFont(appDir + @"\SyntaxLTStd-Roman.otf", BaseFont.IDENTITY_H, BaseFont.EMBEDDED);
            bfBlack = BaseFont.CreateFont(appDir + @"\SyntaxLTStd-Black.otf", BaseFont.IDENTITY_H, BaseFont.EMBEDDED);

            bfArialBold = BaseFont.CreateFont(appDir + @"\arialbd.ttf", BaseFont.IDENTITY_H, BaseFont.EMBEDDED);
            bfArial = BaseFont.CreateFont(appDir + @"\arial.ttf", BaseFont.IDENTITY_H, BaseFont.EMBEDDED);
            bfArialItalic = BaseFont.CreateFont(appDir + @"\ariali.ttf", BaseFont.IDENTITY_H, BaseFont.EMBEDDED);
            bfArial.Subset = true;
            bfBold.Subset = true;

            // Assume page in odd
            margins = new ChopMargin();
            margins.Left = 60f;
            margins.Right = 45f;
            margins.Top = 34f;
            margins.Bottom = 44f;

            // Create additional international text
            internationalExclude = new MultiLang("EXKL.");
            internationalExclude.UpdateForLanguage(LangSupport.FRENCH_ID, "EXCL.");
            internationalExclude.UpdateForLanguage(LangSupport.ITALIAN_ID, "ESCL.");

            internationalInclude = new MultiLang("INKL.");
            internationalInclude.UpdateForLanguage(LangSupport.FRENCH_ID, "INCL.");
            internationalInclude.UpdateForLanguage(LangSupport.ITALIAN_ID, "INCL.");

            internationalNote = new MultiLang("Beachte:");
            internationalNote.UpdateForLanguage(LangSupport.FRENCH_ID, "Note:");
            internationalNote.UpdateForLanguage(LangSupport.ITALIAN_ID, "Nota:");

            internationalCodeAlso = new MultiLang("Kodiere ebenso:");
            internationalCodeAlso.UpdateForLanguage(LangSupport.FRENCH_ID, "Coder aussi:");
            internationalCodeAlso.UpdateForLanguage(LangSupport.ITALIAN_ID, "Codificare anche:");

        }
        #endregion

        #region Font Initialisation

        private void FontInitialize(bool useSmallFont)
        {
            if (useSmallFont == false)
            {
                fontCode = new Font(bfBold, 8f, Font.NORMAL);
                fontExclude = new Font(bfItalic, 8f, Font.NORMAL);
                fontRemark = new Font(bfRoman, 8f, Font.NORMAL);
                fontExcludeTag = new Font(bfArialBold, 4f, Font.NORMAL, BaseColor.WHITE);
                fontIncludeTag = new Font(bfArial, 4f, Font.NORMAL, BaseColor.BLACK);
                fontHeader = new Font(bfRoman, 8f, Font.NORMAL, BaseColor.BLACK);
                fontHeaderLevel1 = new Font(bfBold, 8f, Font.NORMAL);
                fontFooter = new Font(bfBold, 7f, Font.NORMAL, BaseColor.BLACK);
            }
            else
            {
                fontCode = new Font(bfArialBold, 7f, Font.NORMAL);
                fontExclude = new Font(bfArialItalic, 7f, Font.NORMAL);
                fontRemark = new Font(bfArial, 7f, Font.NORMAL);
                fontExcludeTag = new Font(bfArialBold, 4f, Font.NORMAL, BaseColor.WHITE);
                fontIncludeTag = new Font(bfArial, 4f, Font.NORMAL, BaseColor.BLACK);
                fontHeader = new Font(bfArial, 7f, Font.NORMAL, BaseColor.BLACK);
                fontHeaderLevel1 = new Font(bfArialBold, 8f, Font.NORMAL);
                fontFooter = new Font(bfBold, 7f, Font.NORMAL, BaseColor.BLACK);
            }
        }
        #endregion

        #region Static Helper Dico of CHOP Codelists

        /// <summary>
        /// Provides a dictionnary for holding all Codelists used in CHOP representation
        /// </summary>
        /// <param name="sm">Artefact container loader</param>
        /// <param name="hierarchy">hierarchy od CHOP</param>
        /// <returns>a dictionary with all codelists</returns>
        public static Dictionary<string, CodeListType> GetRefDictionary(SmManager sm, HierarchyType hierarchy)
        {
            Dictionary<string, CodeListType> dico = new Dictionary<string, CodeListType>();
            foreach (CodelistRefType crt in hierarchy.CodelistRefList)
            {
                CodeListType clType = null;
                Artefact a = sm.GetArtefact(SdmxArtefactType.CodeLists, crt);
                if (a != null)
                    clType = a as CodeListType;
                if (clType != null)
                    dico.Add(crt.Alias, clType);
            }

            return dico;
        }
        #endregion

        #region Code Table creation

        /// <summary>
        /// Recursive method for creating a PDF table of each code of the CHOP hierarchy
        /// </summary>
        /// <param name="parentCode">Id of parent code</param>
        /// <param name="crt">Reference to code and codelist of current code</param>
        /// <param name="level">CHOP hierarchy level treated</param>
        protected void FillCodeTable(string parentCodeId, CodeRefType crt, int level)
        {
            // 1st Level is used for page header in CHOP document
            if (level > 0)
            {
                CodeListType clType = dico[crt.CodelistAliasRef];
                ISdmxHierarchyListItem item = clType.CodeList.LocateItem(crt.CodeID);

                if (item == null)
                    throw new ApplicationException("Unable to proceed. Hierarchy referred code Id '" +
                        crt.CodeID + "' is not present in codelist: " + clType.FullIdent);

                CodeType code = item as CodeType;
                PdfPTable tb = CodeInfo(code, level);

                //>TODO: If height bigger than half page height, create multiple tables
                float headFoot = margins.Top + margins.Bottom;
                if (pdfOptions.SplitLargeTable && tb.TotalHeight > doc.PageSize.Height - 2*headFoot)
                    TableInnerSplit(tb, code, parentCodeId, level);
                else
                    codeTableList.Add(new CodeTableRepresentation(code, parentCodeId, tb, level));
            }

            // Recurse
            foreach (CodeRefType it in crt.CodeRefList)
                FillCodeTable(crt.CodeID, it, level + 1);
        }

        private void TableInnerSplit(PdfPTable tb, CodeType code, string parentCodeId, int level)
        {
            if (tb.Rows.Count > 0)
            {
                for (int rowIndex = 0; rowIndex < tb.Rows.Count; rowIndex++)
                {
                    PdfPTable tbRow = new PdfPTable(tb);
                    PdfPRow row = tbRow.Rows[rowIndex];
                    tbRow.Rows.Clear();
                    tbRow.Rows.Add(row);
                    tbRow.CalculateHeights(true);
                    codeTableList.Add(new CodeTableRepresentation(code, parentCodeId, tbRow, level));
                }
            }
            else
                codeTableList.Add(new CodeTableRepresentation(code, parentCodeId, tb, level));
        }

        private void EnsureTextInCurrentlang(AnnotationList anList)
        {
            List<SdmxMl.Common.Annotation> removeList = new List<SdmxMl.Common.Annotation>();
            foreach (SdmxMl.Common.Annotation a in anList)
            {
                // Can be empty text in some language
                if (a.Text.ToString().Length == 0)
                    removeList.Add(a);
            }

            foreach (SdmxMl.Common.Annotation a in removeList)
                anList.Remove(a);
        }

        /// <summary>Creates all informations for a CHOP code</summary>
        /// <param name="c">the code information</param>
        /// <param name="level">the current hierarchy level</param>
        /// <returns>a PDF table holdings the information</returns>
        private PdfPTable CodeInfo(CodeType c, int levelHierarchy)
        {
            PdfPTable table = new PdfPTable(5);

            // Requirement MH on email 25.06.2013 12:27
            // Indentation acording to code size or by hierarchy level
            int level = pdfOptions.IndentByLevel ? levelHierarchy :
                levelHierarchy > 1 ? c.Id.Length-1 : 1;
        
            // Requirement MH on eMail 04.07.2013 (bad understanding, comment out)
            // align 3 and 2 chars and level 4 is as 2
            //if (pdfOptions.IndentByLevel == false && pdfOptions.BeautifyAlign)
            //{
            //    if (level > 1)
            //        level -= 1;
            //}

            float indent = level == 1 ? 1f : 1f + 10f * level;            
            float indentCode = 36f + level * 18f;

            float[] widths = new float[] { indent, indentCode, 95f, 70f, 495f - indent - indentCode };
            table.TotalWidth = 240f;

            table.LockedWidth = true;
            table.SetWidths(widths);
            table.DefaultCell.Border = Rectangle.NO_BORDER;

            // Id and title
            table.AddCell(string.Empty);
            Phrase p = new Phrase(BeautifyCodeId(c.Id), fontCode);
            PdfPCell cell = new PdfPCell(p);
            cell.Border = Rectangle.NO_BORDER;
            table.AddCell(cell);
            string text = c.Name.ToString();
            if (c.AnnotationList.GetAnnotationType(ChopCodes.ANNOT_TYPE_LATERAL) != null)
                text += " [L]";
            p = new Phrase(text, fontCode);
            cell = new PdfPCell(p);
            cell.Colspan = 3;
            cell.Border = Rectangle.NO_BORDER;
            table.AddCell(cell);

            // Remarks
            AnnotationList anl = c.AnnotationList.GetAllAnnotationOfType(ChopCodes.ANNOT_TYPE_REMARK, null);
            anl.Sort(delegate(SdmxMl.Common.Annotation p1, SdmxMl.Common.Annotation p2)
            { return p1.Text.ToString().CompareTo(p2.Text.ToString()); });
            EnsureTextInCurrentlang(anl);
            foreach (SdmxMl.Common.Annotation a in anl)
            {
                table.AddCell(string.Empty);
                table.AddCell(string.Empty);
                p = new Phrase(a.Text.ToString(), fontRemark);
                cell = new PdfPCell(p);
                cell.Colspan = 3;
                cell.Border = Rectangle.NO_BORDER;
                table.AddCell(cell);
            }


            // Include
            anl = c.AnnotationList.GetAllAnnotationOfType(ChopCodes.ANNOT_TYPE_INCLUDE, null);
            anl.Sort(delegate(SdmxMl.Common.Annotation p1, SdmxMl.Common.Annotation p2)
            { return p1.Text.ToString().CompareTo(p2.Text.ToString()); });
            EnsureTextInCurrentlang(anl);
            bool tag = false;
            foreach (SdmxMl.Common.Annotation a in anl)
            {
                table.AddCell(string.Empty);
                table.AddCell(string.Empty);
                if (tag)
                {
                    table.AddCell(string.Empty);
                }
                else
                {
                    tag = true;
                    cell = new PdfPCell(GenIncludeTag());
                    cell.Border = Rectangle.NO_BORDER;
                    cell.HorizontalAlignment = Element.ALIGN_LEFT;
                    cell.VerticalAlignment = Element.ALIGN_UNDEFINED;
                    cell.PaddingTop = 3f;
                    cell.PaddingLeft = 3f;

                    table.AddCell(cell);
                }
                p = new Phrase(a.Text.ToString(), fontRemark);
                cell = new PdfPCell(p);
                cell.Colspan = 2;
                cell.Border = Rectangle.NO_BORDER;
                table.AddCell(cell);
            }

            // Hint
            anl = c.AnnotationList.GetAllAnnotationOfType(ChopCodes.ANNOT_TYPE_HINT, null);
            tag = false;
            anl.Sort(delegate(SdmxMl.Common.Annotation p1, SdmxMl.Common.Annotation p2)
            { return p1.Text.ToString().CompareTo(p2.Text.ToString()); });
            EnsureTextInCurrentlang(anl);
            foreach (SdmxMl.Common.Annotation a in anl)
            {
                table.AddCell(string.Empty);
                table.AddCell(string.Empty);
                if (tag)
                {
                    table.AddCell(string.Empty);
                    table.AddCell(string.Empty);
                }
                else
                {
                    tag = true;
                    p = new Phrase(internationalCodeAlso.ToString(), fontRemark);
                    cell = new PdfPCell(p);
                    cell.Colspan = 2;
                    cell.Border = Rectangle.NO_BORDER;
                    table.AddCell(cell);
                }
                p = new Phrase(a.Text.ToString(), fontRemark);
                cell = new PdfPCell(p);
                //cell.Colspan = 3;
                cell.Border = Rectangle.NO_BORDER;
                table.AddCell(cell);
            }

            // Exclusion
            anl = c.AnnotationList.GetAllAnnotationOfType(ChopCodes.ANNOT_TYPE_EXCLUSION, null);
            anl.Sort(delegate(SdmxMl.Common.Annotation p1, SdmxMl.Common.Annotation p2)
            { return p1.Text.ToString().CompareTo(p2.Text.ToString()); });
            tag = false;
            EnsureTextInCurrentlang(anl);
            foreach (SdmxMl.Common.Annotation a in anl)
            {
                table.AddCell(string.Empty);
                table.AddCell(string.Empty);
                if (tag)
                {
                    table.AddCell(string.Empty);
                }
                else
                {
                    tag = true;
                    cell = new PdfPCell(GenExcludeTag());
                    cell.Border = Rectangle.NO_BORDER;
                    cell.HorizontalAlignment = Element.ALIGN_LEFT;
                    cell.PaddingLeft = 3f;
                    cell.VerticalAlignment = Element.ALIGN_UNDEFINED;
                    cell.PaddingTop = 3f;
                    table.AddCell(cell);
                }
                p = new Phrase(a.Text.ToString(), fontExclude);
                cell = new PdfPCell(p);
                cell.Colspan = 2;
                cell.Border = Rectangle.NO_BORDER;
                table.AddCell(cell);
            }

            // Beachte (NB)
            anl = c.AnnotationList.GetAllAnnotationOfType(ChopCodes.ANNOT_TYPE_NOTE, null);
            anl.Sort(delegate(SdmxMl.Common.Annotation p1, SdmxMl.Common.Annotation p2)
            { return p1.Text.ToString().CompareTo(p2.Text.ToString()); });
            EnsureTextInCurrentlang(anl);
            tag = false;
            foreach (SdmxMl.Common.Annotation a in anl)
            {
                table.AddCell(string.Empty);
                table.AddCell(string.Empty);
                if (tag)
                {
                    table.AddCell(string.Empty);
                }
                else
                {
                    tag = true;
                    p = new Phrase(internationalNote.ToString(), fontRemark);
                    cell = new PdfPCell(p);
                    cell.Border = Rectangle.NO_BORDER;
                    table.AddCell(cell);
                }
                p = new Phrase(a.Text.ToString(), fontRemark);
                cell = new PdfPCell(p);
                cell.Colspan = 2;
                cell.Border = Rectangle.NO_BORDER;
                table.AddCell(cell);
            }
            return table;
        }

        /// <summary>Format the Exclusion marker </summary>
        /// <returns>Pdf Table representing the marker</returns>
        private PdfPTable GenExcludeTag()
        {
            PdfPTable table = new PdfPTable(1);
            float[] widths = new float[] { 1f };
            table.TotalWidth = 16f;

            table.LockedWidth = true;
            table.SetWidths(widths);
            table.DefaultCell.Border = Rectangle.NO_BORDER;

            Phrase p = new Phrase(internationalExclude.ToString(), fontExcludeTag);
            PdfPCell cell = new PdfPCell(p);
            cell.Border = Rectangle.NO_BORDER;
            cell.BackgroundColor = BaseColor.BLACK;
            cell.HorizontalAlignment = Element.ALIGN_CENTER;
            table.AddCell(cell);
            cell.Padding = 0f;
            table.HorizontalAlignment = Element.ALIGN_LEFT;
            return table;
        }

        /// <summary>Format the Inclusion marker </summary>
        /// <returns>Pdf Table representing the marker</returns>
        private PdfPTable GenIncludeTag()
        {
            PdfPTable table = new PdfPTable(1);
            float[] widths = new float[] { 1f };
            table.TotalWidth = 16f;

            table.LockedWidth = true;
            table.SetWidths(widths);

            Phrase p = new Phrase(internationalInclude.ToString(), fontIncludeTag);
            PdfPCell cell = new PdfPCell(p);
            cell.HorizontalAlignment = Element.ALIGN_CENTER;
            table.AddCell(cell);
            cell.Padding = 0f;
            table.HorizontalAlignment = Element.ALIGN_LEFT;
            return table;
        }

        /// <summary>Add dot spearator(s) to code Id</summary>
        /// <param name="codeId">the code id as defined in codelist</param>
        /// <returns>the code with defined Pdf presentation</returns>
        public static string BeautifyCodeId(string codeId)
        {
            StringBuilder sb = new StringBuilder();
            for (int i = 0; i < codeId.Length; i++)
            {
                if (i > 0 && i % 2 == 0)
                    sb.Append('.');
                sb.Append(codeId[i]);
            }
            return sb.ToString();
        }
 

        #endregion

        #region Public method for Pdf Generation

        /// <summary>Pdf generation of CHOP </summary>
        /// <param name="hclChop">CGHOP hierarchy in SDMX</param>
        /// <param name="filename">Target filename for Pdf</param>
        /// <param name="start">Start number for page numbering</param>
        public void Generate(HierarchicalCodelistType hclChop, string filename, PdfOptions options)
        {
            // Retrieve options
            pdfOptions = options;

            // Init fonts according to selected size
            FontInitialize(pdfOptions.UseSmallFonts);

            // Create the PDF document
            doc = new Document();
            try
            {
                sm = ReferenceManager.GetSmContainerImage();

                // Ensure in manager
                hcl = hclChop;
                ArtefactRefTypedList artl = hcl.ArtefactRefList;
                sm.LoadMissingArtefactsFromReferenceMgr(artl);
                sm.LoadMissingArtefactsFromRegistry(artl);

                dico = GetRefDictionary(sm, hcl.HierarchyList[0]);


                docWriter = PdfWriter.GetInstance(doc, new FileStream(filename, FileMode.Create));
                doc.Open();

                // prepares container of Pdf tables for each CHOP code
                codeTableList = new List<CodeTableRepresentation>(1000);

                foreach (CodeRefType it in hcl.HierarchyList[0].CodeRefList)
                    FillCodeTable(string.Empty, it, 0);

                // Now write and calculate distribution on two columns

                CodeListType clType = dico[hcl.HierarchyList[0].CodeRefList[0].CodelistAliasRef];

                DistributeCodes(clType);
            }

            finally
            {
                doc.Close();
            }
        }
        #endregion

        #region Distribution of codes table in Pds pages

        /// <summary>Helper to get concerned top level code (ensures in array limits) </summary>
        /// <param name="clTopLevel">Top level codelist</param>
        /// <param name="index">index of requested code</param>
        /// <returns>the codetype object at index</returns>
        private CodeType GetTopDomainCode(CodeListType clTopLevel, int index)
        {
            if (index >= clTopLevel.CodeList.Count)
                return clTopLevel.CodeList[clTopLevel.CodeList.Count - 1] as CodeType;
            return clTopLevel.CodeList[index] as CodeType;
        }

        /// <summary> Distributes CHOP codes tables in Pds pages </summary>
        /// <param name="clTopLevel">the top level used for header comments / splitting</param>
        private void DistributeCodes(CodeListType clTopLevel)
        {
            float headFoot = margins.Top + margins.Bottom; ;
            int colIndex = 0;
            float curheight = doc.PageSize.Height - headFoot;
            int page = pdfOptions.StartPage;
            int currentHeadCodeIndex = 0;

            // First header if even page, switch margin
            if (page % 2 == 0)
                margins.SwitchMargin();

            PdfPTable tbHead = HeaderTable(headFoot, 0, GetTopDomainCode(clTopLevel, currentHeadCodeIndex));
            tbHead.WriteSelectedRows(0, -1, margins.Left, doc.PageSize.Height - margins.Top, docWriter.DirectContent);

            // distribute in 2 columns
            foreach (CodeTableRepresentation ctb in codeTableList)
            {
                // Header code change ?

                // Due to top level domain ?
                if (ctb.Level == 1 && ctb.ParentCodeId != clTopLevel.CodeList[currentHeadCodeIndex].Id)
                {
                    ++currentHeadCodeIndex;
                    // Add footer
                    PdfPTable tbFooter = FooterTable(headFoot, page++);
                    tbFooter.WriteSelectedRows(0, -1, margins.Left, margins.Bottom, docWriter.DirectContent);

                    // next page
                    doc.NewPage();
                    margins.SwitchMargin();

                    // Add header
                    int codeIndex = codeTableList.IndexOf(ctb);
                    tbHead = HeaderTable(headFoot, codeIndex, GetTopDomainCode(clTopLevel, currentHeadCodeIndex));
                    tbHead.WriteSelectedRows(0, -1, margins.Left, doc.PageSize.Height - margins.Top, docWriter.DirectContent);

                    colIndex = 0;
                    curheight = doc.PageSize.Height - headFoot;
                }
                else
                {
                    // Due to remsaining space in page
                    if (curheight - ctb.Table.TotalHeight < headFoot)
                    {
                        if (colIndex == 0)
                        {
                            colIndex = 1;
                            curheight = doc.PageSize.Height - headFoot;
                        }
                        else
                        {
                            // Add footer
                            PdfPTable tbFooter = FooterTable(headFoot, page++);
                            tbFooter.WriteSelectedRows(0, -1, margins.Left, margins.Bottom, docWriter.DirectContent);

                            // next page
                            doc.NewPage();
                            margins.SwitchMargin();

                            // Add header
                            int codeIndex = codeTableList.IndexOf(ctb);
                            tbHead = HeaderTable(headFoot, codeIndex, GetTopDomainCode(clTopLevel, currentHeadCodeIndex));
                            tbHead.WriteSelectedRows(0, -1, margins.Left, doc.PageSize.Height - margins.Top, docWriter.DirectContent);

                            colIndex = 0;
                            curheight = doc.PageSize.Height - headFoot;
                        }
                    }
                }

                float loc = colIndex == 0 ? margins.Left : margins.Left/2 + doc.PageSize.Width/ 2 - 20;
                ctb.Table.WriteSelectedRows(0, -1, loc, curheight, docWriter.DirectContent);
                curheight -= ctb.Table.TotalHeight;

            }
            // Add final footer
            FooterTable(headFoot, page).WriteSelectedRows(0, -1, margins.Left, margins.Bottom, docWriter.DirectContent);
        }

        #endregion

        #region Header Table

        private PdfPTable HeaderTable(float headFoot, int codeIndex, CodeType cTop)
        {
            PdfPTable table = new PdfPTable(3);
            float[] widths = new float[] { 8f, 82f, 8f };
            table.SetWidths(widths);
            table.TotalWidth = doc.PageSize.Width - margins.Left - margins.Right;
            CodeType codeTop = codeTableList[codeIndex].Code;
            CodeType codeBottom = null;
            int colIndex = 0;
            float curheight = doc.PageSize.Height - headFoot;

            // Locate last in page
            for (int i = codeIndex; i < codeTableList.Count; ++i)
            {
                if (codeTableList[i].Level == 1 && codeTableList[i].ParentCodeId != cTop.Id)
                {
                    codeBottom = codeTableList[i - 1].Code;
                    break;
                }
                else
                {
                if (curheight - codeTableList[i].Table.TotalHeight < headFoot)
                {
                    if (colIndex == 0)
                    {
                        colIndex = 1;
                        curheight = doc.PageSize.Height - headFoot;
                    }
                    else
                    {
                        // found first in next page
                        codeBottom = codeTableList[i - 1].Code;
                        break;
                    }
                }
                curheight -= codeTableList[i].Table.TotalHeight;
                }
            }

            if (codeBottom == null)
                codeBottom = codeTableList[codeTableList.Count - 1].Code;

            // 1st cell is ID of codeTop
            Phrase p = new Phrase(BeautifyCodeId(codeTop.Id), fontHeader);
            PdfPCell cell = new PdfPCell(p);
            cell.Border = Rectangle.BOTTOM_BORDER;
            table.AddCell(cell);

            // 2nd cell Is level one hierarchy
            p = new Phrase(cTop.Id + "  " + cTop.Name.ToString(), fontHeaderLevel1);
            cell = new PdfPCell(p);
            cell.Border = Rectangle.BOTTOM_BORDER;
            cell.HorizontalAlignment = Element.ALIGN_CENTER;
            table.AddCell(cell);

            // third cell is ID of codeBottom
            p = new Phrase(BeautifyCodeId(codeBottom.Id), fontHeader);
            cell = new PdfPCell(p);
            cell.Border = Rectangle.BOTTOM_BORDER;
            cell.HorizontalAlignment = Element.ALIGN_RIGHT;
            table.AddCell(cell);


            return table;
        }
        #endregion

        #region FooterTable

        private PdfPTable FooterTable(float headFoot, int pageNr)
        {
            PdfPTable table = new PdfPTable(2);
            float[] widths = new float[] { 50f, 50f };
            table.TotalWidth = doc.PageSize.Width - margins.Left - margins.Right;

            string left = pageNr % 2 == 0 ? pageNr.ToString() : pdfOptions.BottomText;
            string right = pageNr % 2 == 0 ? pdfOptions.BottomText : pageNr.ToString();
            if (pdfOptions.SuppressNumbering)
            {
                left = right = string.Empty;
            }
            // 1st cell is number if even, else user defined bottom text
            Phrase p = new Phrase(left, fontFooter);
            PdfPCell cell = new PdfPCell(p);
            cell.Border = Rectangle.NO_BORDER;
            table.AddCell(cell);

            // 2nd cell is number if off, else user defined bottom text
            p = new Phrase(right, fontFooter);
            cell = new PdfPCell(p);
            cell.Border = Rectangle.NO_BORDER;
            cell.HorizontalAlignment = Element.ALIGN_RIGHT;
            table.AddCell(cell);


            return table;
        }

        #endregion

    }

    #region Class CodeTableRepresentation

    /// <summary> 
    /// A code and it's table representation. 
    /// Used to distribution in PDF documentpages 
    /// </summary>
    public class CodeTableRepresentation
    {
        public string ParentCodeId { get; private set; }
        public CodeType Code { get; private set; }
        public PdfPTable Table { get; private set; }
        public int Level { get; private set; }

        public CodeTableRepresentation(CodeType c, string parentId, PdfPTable tb, int hierarchyLevel)
        {
            Code = c;
            ParentCodeId = parentId;
            Table = tb;
            Level = hierarchyLevel;
        }
    }
    #endregion
}
